package metrictransformer

import (
	"encoding/json"
	"errors"
	"fmt"

	ms "gitee.com/shipnet/flogo_metric_store"
	"github.com/tidwall/gjson"

	"github.com/project-flogo/core/activity"
	"github.com/project-flogo/core/data/coerce"
	"github.com/project-flogo/core/data/metadata"
)

type Activity struct {
	// store MetricStore
	// settings ActivitySettings
	mappers []mapperHandle
}

func init() {
	_ = activity.Register(&Activity{}, NewActivity)
}

func NewActivity(ctx activity.InitContext) (activity.Activity, error) {
	settings := ActivitySettings{}
	err := metadata.MapToStruct(ctx.Settings(), &settings, true)
	if err != nil {
		return nil, err
	}
	mappers := []mapperHandle{}
	for _, gm := range settings.Mappings {
		mapping, err := toMapping(gm)
		if err != nil {
			return nil, err
		}
		t, err := buildMapping(mapping)
		if err != nil {
			return nil, err
		}
		mappers = append(mappers, t)
	}
	act := Activity{
		mappers: mappers,
	}
	return &act, nil
}

var md = activity.ToMetadata(&Input{}, &Output{}, &ActivitySettings{})

func (a *Activity) Metadata() *activity.Metadata {
	return md
}

func (a *Activity) Eval(ctx activity.Context) (bool, error) {
	var err error
	input := Input{}
	err = ctx.GetInputObject(&input)
	if err != nil {
		return true, err
	}

	ms := []ms.MetricData{}
	values := toMap(input.Data)
	for _, mapper := range a.mappers {
		md, err := evalMapper(&mapper, &input.Data, values)
		if err == nil {
			if md != nil {
				ms = append(ms, *md)
			}
		} else {
			ctx.Logger().Warn(err)
		}
	}
	output := Output{
		Err:     errToString(err),
		Metrics: ms,
	}
	err = ctx.SetOutputObject(&output)

	return true, err
}

func errToString(err error) string {
	if err == nil {
		return ""
	}
	return err.Error()
}

func buildMapping(mapping Mapping) (mapperHandle, error) {
	filter, err := buildFilter(mapping.Filter)
	if err != nil {
		return mapperHandle{}, err
	}
	mapID, err := buildTransformer(&mapping.MapID)
	if err != nil {
		return mapperHandle{}, err
	}
	if mapID == nil {
		return mapperHandle{}, errors.New("must map to new metric")
	}
	mapTimestamp, err := buildTransformer(mapping.MapTimestamp)
	if err != nil {
		return mapperHandle{}, err
	}
	mapValue, err := buildTransformer(mapping.MapValue)
	if err != nil {
		return mapperHandle{}, err
	}

	m := mapperHandle{
		filter:       filter,
		mapID:        mapID,
		mapTimestamp: mapTimestamp,
		mapValue:     mapValue,
	}
	return m, nil
}

func buildFilter(options MappingFilterOptions) (Filter, error) {
	switch options.Type {
	case "EXPR":
		{
			return NewExprFilter(options.Params)
		}
	case "REGEXP":
		{
			return NewRegexpFilter(options.Params)
		}
	default:
		{
			return NewPlainFilter(options.Params)
		}
	}
}

func buildTransformer(options *MappingTransformerOptions) (Transformer, error) {
	if options == nil {
		return nil, nil
	}
	switch options.Type {
	case "EXPR":
		{
			return NewExprTransformer(options.Params)
		}
	default:
		{
			return nil, fmt.Errorf("invalid transformer type: %s", options.Type)
		}
	}
}

func evalMapper(mh *mapperHandle, data *ms.MetricData, values map[string]interface{}) (*ms.MetricData, error) {
	if !mh.filter.Apply(*data) {
		return nil, nil
	}

	id, err := evalMapperID(mh.mapID, data, values)
	if err != nil {
		return nil, err
	}

	timestamp, err := evalMapperTimestamp(mh.mapTimestamp, data, values)
	if err != nil {
		return nil, err
	}

	value, err := evalMapperValue(mh.mapValue, data, values)
	if err != nil {
		return nil, err
	}

	md := ms.MetricData{
		ID:        id,
		Timestamp: timestamp,
		Value:     value,
	}

	return &md, nil
}

func evalMapperID(trans Transformer, data *ms.MetricData, values map[string]interface{}) (string, error) {
	gnid, err := trans.Trans(values)
	if err != nil {
		return "", fmt.Errorf("metric transform id failed: %v", err)
	}
	nid, err := coerce.ToString(gnid)
	if err != nil {
		return "", fmt.Errorf("metric transform id failed: %v", err)
	}
	if nid == "" {
		return "", errors.New("metric transform id failed: transform to empty id")
	}
	return nid, nil
}

func evalMapperTimestamp(trans Transformer, data *ms.MetricData, values map[string]interface{}) (int64, error) {
	var nt int64
	if trans != nil {
		gnt, err := trans.Trans(values)
		if err != nil {
			return 0, fmt.Errorf("metric transform timestamp failed: %v", err)
		}
		nt, err = coerce.ToInt64(gnt)
		if err != nil {
			return 0, fmt.Errorf("metric transform timestamp failed: %v", err)
		}
	} else {
		nt = data.Timestamp
	}
	return nt, nil
}

func evalMapperValue(trans Transformer, data *ms.MetricData, values map[string]interface{}) (gjson.Result, error) {
	var nv gjson.Result
	if trans != nil {
		gnv, err := trans.Trans(values)
		if err != nil {
			return nv, fmt.Errorf("metric transform value failed: %v", err)
		}
		b, err := json.Marshal(gnv)
		if err != nil {
			return nv, fmt.Errorf("metric transform value failed: %v", err)
		}
		nv = gjson.ParseBytes(b)
	} else {
		nv = data.Value
	}
	return nv, nil
}
